#!/usr/bin/python
#-*-coding: utf-8 -*-

__all__ = [u'GuiPart']

########## DO NOT CHANGE THESE LINES ###########################################
import sys
from pylot.core import (ENCODING, Path)
try : __file__
except NameError : co = sys._getframe(0).f_code ; __file__ = co.co_filename
ufile = __file__.decode(ENCODING)
if ufile.strip() : PLUGIN_PATH = Path(ufile).abspath().parent
else : PLUGIN_PATH = Path(u'.').abspath()
########## DO NOT CHANGE THESE LINES ###########################################

from PyQt4.QtCore import SIGNAL, Qt
from PyQt4.QtGui import (QDialog, QFileDialog, QWidget,
                         QAction, QMenu, QToolBar, QFont,
                         QTreeWidget, QTreeWidgetItem, QBrush, QMessageBox,
                         QTreeWidgetItemIterator)

from _pylot.qtgui.misc import load_designer_file
from _pylot.qtgui.interfaces.widgetinterface import WidgetInterface
from pylot.core import (Datum, Book, value_to_pylot_str, StrList)

from handler import HandlerPart
from parameters import Parameters

_GuiParameters, widget = load_designer_file(u'parameters.ui', [PLUGIN_PATH])
class GuiPart(QWidget, _GuiParameters, WidgetInterface):
  u"""
  GuiParameters implémente un éditeur de paramètres dans un QTreeWidget
  
  - 11-2008, Pierre Puiseux,
  - 03-2009, Pierre Puiseux
  - 05-2009, Pierre Puiseux, Guillaume Baty
  
  Université de Pau et des Pays de l'Adour, INRIA
  
  .. TODO:: mettre les tooltip. Voir file:///usr/share/doc/qt4-doc-html/html/widgets-tooltips.html
  
  .. TODO:: implémenter l'envoi de SIGNAL lors de toute modif de tree_widget.
     Et connecter ce SIGNAL à une mise à jour de self.handler, et de self.text_edit
  
  La classe :class:`~pylot.tools.Parameters` fournit la structure de données et les fonctionnalités, a savoir :
      
      - Une :class:`~pylot.tools.Collection` (dictionnaire) de :class:`~pylot.tools.Book`  chaque book contient un Blocks
      - Chaque Block est un dictionnaire de :class:`~pylot.tools.Datum` chaque datum contient un paramètres
      - Chaque paramètre a une value et une value_list
      - La Collection est représentée dans le :attr:`tree_widget`
  
  Les évènements souris ou clavier (signaux Qt) pris en compte sont
    
    - itemChanged (item,column) : lorsque l'item est edité, son contenu a donc changé
    - itemActivated (item,column) : un item activé (enter ou double click)
    - itemDoubleClicked (item, column) : item double cliqué
    - currentItemChanged (current,previous) : l'item courant change (on passe d'un item à un autre)
    - customContextMenuRequested (QPoint) : clic bouton droit
  
  """

  def init(self, handler=None):#fonction inutile, uniquement pour caser la doc
    u"""
    Le constructeur effectue les actions suivantes :
      - Il s'approprie le pathbook
      - crée les menus, tool bar
      - un peu de cosmétique
      
    c'est tout
    
    Pour obtenir une classe fonctionnelle, il faut lui affecter un handler, de type :class;`~pylot.tools.Parameters`
    à l'aide de la méthode :meth:`setHandler`
    La création et affichage des contenus (QTreeWidget) ne sera déclenchée qu'après affectation du handler
    
    :param pathbook: le pathbook (doit contenir l'entrée *icons*, pas plus)
    :type pathbook: PathBook
    """

  def __init__(self, handler=None):
    u"""
    Le constructeur effectue les actions suivantes :
      - crée les menus, tool bar
      - un peu de cosmétique
    c'est tout.
    
    Pour obtenir une classe fonctionnelle, il faut lui affecter un handler, de type :class;`~pylot.tools.Parameters`
    à l'aide de la méthode :meth:`setHandler`
    La création et affichage des contenus (QTreeWidget) ne sera déclenchée qu'après affectation du handler
    """
    if handler is None :
      handler = HandlerPart()
      handler.init()
    WidgetInterface.__init__(self, handler)

    QWidget.__init__(self)
    self.setupUi(self) # Designer file initialization

    self._plugin_path = PLUGIN_PATH

    self.block_color = Qt.yellow
    self.var_font = QFont()
    self.var_font.setBold(False)
    self.var_font.setItalic(True)
    self.var_font_color = Qt.blue
    self.val_color = Qt.white
    self._item_edited = None
    self._item_clicked = None
    self._current_expand_level = 0

    self._setHandler(handler)

    self._createActions()
    self._createConnexions()
    self._createMenus()
    self._createToolBars()
    self.c=[]

  def _createActions(self):
    u"""
    Les actions du widget sont crées dans le designer, décorées ici.
    :todo: utiliser l'editeur de ressources de designer pour decorer les icônes.
    """
    self.tree_widget.setContextMenuPolicy(Qt.CustomContextMenu)

  def _createConnexions(self):
    u"""
    Connexions des actions/signaux aux méthodes.
    """
    self.connect(self.action_show_infos,
                 SIGNAL("triggered()"), self.infos)
    self.connect(self.action_save_as_param,
                 SIGNAL("triggered()"), self.saveParametersAs)
    self.connect(self.action_update_db_from,
                 SIGNAL("triggered()"), self.updateFrom)
    self.connect(self.action_save_db,
                 SIGNAL("triggered()"), self.saveDatabase)
    self.connect(self.action_save_as_db,
                 SIGNAL("triggered()"), self.saveDatabaseAs)

    self.connect(self.action_save, SIGNAL("triggered()"), self.handler.saveAll)

    self.connect(self.action_reload_db,
                 SIGNAL("triggered()"), self.reloadDatabase)
    self.connect(self.action_load_db,
                 SIGNAL("triggered()"), self.loadDatabase)
    self.connect(self.action_new_block,
                 SIGNAL("triggered()"), self.newBlock)
    self.connect(self.action_new_param,
                 SIGNAL("triggered()"), self.newParameter)
    self.connect(self.action_duplicate_param,
                 SIGNAL("triggered()"), self.duplicateParameter)
    self.connect(self.action_duplicate_block,
                 SIGNAL("triggered()"), self.duplicateBlock)
    #menu contextuel
    self.tree_widget.setContextMenuPolicy(Qt.CustomContextMenu)
    self.connect(self.tree_widget,
                 SIGNAL("customContextMenuRequested ( const QPoint & )"),
                 self._tree_widgetContextMenu)
    self.connect(self.action_suppr,
                 SIGNAL("triggered()"), self.suppr)
    self.connect(self.action_set_value,
                 SIGNAL("triggered()"), self.setValue)
    self.connect(self.action_collapse,
                 SIGNAL("triggered()"), self.collapse)
    self.connect(self.action_expand_params,
                 SIGNAL("triggered()"), self.expandParams)
    self.connect(self.action_expand_values,
                 SIGNAL("triggered()"), self.expandValues)
    self.connect(self.action_adjust_columns,
                 SIGNAL("triggered()"), self.adjustColumns)
    self.connect(self.tree_widget,
                 SIGNAL("itemDoubleClicked ( QTreeWidgetItem * , int )"),
                 self._onItemActivated)
                 #self._on_itemDoubleClicked)
    self.connect(self.tree_widget,
                 SIGNAL("itemActivated ( QTreeWidgetItem * , int )"),
                 self._onItemActivated)
    self.connect(self.tree_widget,
                 SIGNAL("itemChanged ( QTreeWidgetItem * , int )"),
                 self._onItemContentChanged)
    self.connect(self.tree_widget,
                 SIGNAL("currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem * )"),
                 self._onCurrentItemChanged)

  def _createMenus(self):
    u"""
    Creation du menu.

    L'affichage du menu est assuré par l'appelant.

    Le widget GuiParameters n'affiche rien.
    """
    self.menu_parameters = QMenu()
    self.menu_parameters.setObjectName(u"menu_parameters")
    self.addMenu_(self.menu_parameters)

    self.menu_help = QMenu()
    self.menu_help.setObjectName(u"menu_help")
    self.addMenu_(self.menu_help)

  def _createToolBars(self):
    u"""
    Création d'une QToolbar, sans affichage.

    L'affichage de la tool_bar est assuré par l'appelant.

    Le widget GuiParameters n'affiche rien.
    """
    self.tool_bar_parameters = QToolBar()
    self.tool_bar_parameters.setObjectName(u"tool_bar_parameters")
    self.addToolBar_(self.tool_bar_parameters)
    u"""
    La barre d'outils contenant les actions save_xx, save_as_xx, load_xx,
    reload_xx, ou xx in {'param', 'db'}
    :type: QToolBar
    """

  def _switchUserLevel(self, level):
    pass

  def _translate(self):
    self._setTexts([
      (self.menu_parameters, self.tr_(u'&Parameters')),
      (self.tool_bar_parameters, self.tr_(u'Parameters ToolBar')),
      ])

  def _updateData(self):
    pass

  def _refresh(self):
    self._toTreeView()

  def _decorate(self):
    self._setIcons([
      (self.action_show_infos, u'pylotInfos.png'),

      (self.action_load_db, u'dbLoad.png'),
      (self.action_reload_db, u'dbRefresh.png'),
      (self.action_save_as_db, u'dbSaveAs.png'),
      (self.action_save_as_param, u'paramSaveAs.png'),
      (self.action_save_db, u'dbSave.png'),
      (self.action_save, u'dbSave.png'),
      (self.action_update_db_from, u'dbUpdate.png'),
      (self.action_validate, u'paramSave.png'),

      #menu contextuel
      (self.action_adjust_columns, u'paramAdjust.png'),
      (self.action_block_down, u'paramDown.png'),
      (self.action_block_up, u'paramUp.png'),
      (self.action_collapse, u'paramCollapse.png'),
      (self.action_duplicate_block, u'duplicate.png'),
      (self.action_duplicate_param, u'duplicate.png'),
      (self.action_expand_params, u'paramExpand.png'),
      (self.action_expand_values, u'paramExpand.png'),
      (self.action_new_block, u'paramInsert.png'),
      (self.action_new_param, u'paramInsert.png'),
      (self.action_set_value, u'paramSet.png'),
      (self.action_sort_blocks, u'paramSort.png'),
      (self.action_suppr, u'paramSuppr.png'),
    ])

  def _fillMenus(self, level):
    self._fill(self.menu_parameters, [
      self.action_save_db,
      self.action_save_as_param,
      u'-',
      self.action_save,
      u'-',
      self.action_reload_db,
      self.action_update_db_from,
      self.action_load_db,
      self.action_save_as_db,
      u'-',
    ])

    self._fill(self.menu_help, [self.action_show_infos])

  def _fillToolBars(self, level):
    self._fill(self.tool_bar_parameters, [
      self.action_save,
      self.action_save_as_param,
      u'-',
      self.action_reload_db,
      self.action_update_db_from,
      self.action_load_db,
      self.action_save_as_db,
      u'-',
      self.action_show_infos,
    ])

  def _readSettings(self, settings, common):
    pass

  def _writeSettings(self, settings, common):
    pass

  def _quit(self):
    pass

  def infos(self):
    GuiInfos, WidgetClass = load_designer_file(u'infos.ui', [PLUGIN_PATH])
    w = QDialog(self)
    i = GuiInfos()
    i.setupUi(w)
    w.setWindowTitle(self.tr_(u"pyLot Parameters"))
    infos = ''
    for key, val in self.handler.data.infos().iteritems():
      infos += '<br><strong>%s </strong> : %s ' % (key, val)
    i.textBrowserInfo.setText(infos)
    w.setModal(False)
    w.show()

  def _documentation(self):
    u"""
    Ne doit jamais être appelée.
    Sert uniquement pour pouvoir caser la doc de tree_widget et text_edit pour être certain qu'elle soit 'parsée' par epydoc.
    """
    raise IOError (u"La méthode GuiParameters._documentation ne doit jamais être appelée ")
    if False :
      self.tree_widget = 0
      u"""
      Visualisation et navigation dans :class:`Parameters` sous forme de QTreeWidget
      :type: QTreeWidget
      """

  def findItem(self, c):
    u"""
    Retourne l'item correspondant à c.
    
    normalement c'est la fonction inverse de :meth:`content`:
    
    self.item(self.content(item)) == item et
    
    self.content(self.item(c)) ==c
    
    :param c: liste de taille 0,1,2 ou 3 [[block,[param,[value]]]]
    :type c: liste de unicodes
    :return item: un QTreeWidgetItem correspondant à c
    
    .. seealso::  :meth:`content`
    
    """
    if len(c) == 0 : return self
    try : block_item = self.tree_widget.findItems(c[0], Qt.MatchExactly, 0)[0]#les items contenant c[0] en colonne 0
    except IndexError : return None

    #print '### GuiParameters.findItem(%s) : %s' % (c, self.content(block_item))
    if len(c) == 1: return block_item

    for i in range(block_item.childCount()) :
      item = block_item.child(i)
      ci = self.content(item)
      if ci [1] == c[1] :
        if len(c) == 2 : return item
#        print '    GuiParameters.findItem(%s) : %s' % (c, ci)
        for j in range(item.childCount()):
          jtem = item.child(j)
          cj = self.content(jtem)
          if cj[2] == c[2]:
            if len(c) == 3 : return jtem
#            print '    GuiParameters.findItem(%s) : %s' % (c, cj)

    return None

  def isBlock(self, item):
    c = self.content(item)
    return len(c) == 1
  def isParam(self, item):
    c = self.content(item)
    return len(c) == 2
  def isValue(self, item):
    c = self.content(item)
    return len(c) == 3

  def content(self, item):
    u"""
    Renvoit sous forme de liste une description de l'item.

    :param item: un item du QTreeWidget.
    :type item: QTreeWidget
    :return: les textes de l'item et de ses parents sous la forme :

        - [Block,Variable,Valeur] si l'on a cliqué sur une valeur sur une ligne autre que celle portant le nom du paramètre ex : _ 13
        - [Block,Variable] si l'on a cliqué sur la ligne portant le nom du paramètre ex : toto 12
        - [Block] si l'on a cliqué sur la ligne portant le nom d'un block

    :type c: list de str

    Par exemple::
    
        apres un click sur la ligne, content vaut : 
        
        Block    |                    =>       [u'Block']
         -param1 |val0                =>       [u'Block',u'param1']
                 |val2                =>       [u'Block',u'param1',u'val2']
                 |val0                =>       [u'Block',u'param1',u'val0']
                 |val3                =>       [u'Block',u'param1',u'val3']
         -param2 |valeur_actuelle
                 |valeur2
                 |valeur3
        Block2   |
        etc...
        
    .. seealso::  :meth:`findItem`
    
    """
    c = []
    try : #On prend la colonne 0 si elle contient du texte, sinon on prend la colonne 1
      # col = 0 if unicode(item.text(0)): else 1
      col = 1
      if unicode(item.text(0)): col =0
      c.append(unicode(item.text(col)))
    except AttributeError:pass
    try :
      col = 1
      if unicode(item.parent().text(0)): col =0
      # col = 0 if unicode(item.parent().text(0)) else 1
      c.append(unicode(item.parent().text(col)))
    except AttributeError:pass
    try :
      col = 1
      if unicode(item.parent().parent().text(0)): col =0
      # col = 0 if unicode(item.parent().parent().text(0)) else 1
      c.append(unicode(item.parent().parent().text(col)))
    except AttributeError:pass
    c.reverse()
    return c

  def clear(self):
    u"""Nettoyage affichage"""
    self.text_edit.clear()
    self.tree_widget.clear()

  def saveDatabaseAs(self):
    u"""
    Sauvegarde la database vers...
    et en fait la database actuelle
    """
    if self.handler.database_file :
      default_dir = self.handler.database_file.abspath().dirname
    else :
      default_dir = Path(u'.').abspath()
    db = QFileDialog.getSaveFileName(self, self.tr_(u"Save database as..."),
      default_dir, self.tr_(u'Text files (*.pylotdb *.csv)'))
    if not db : return
    db = Path(unicode(db))
    ext = db.ext.lower().strip('.')
    if ext in [u'pylotdb']:
      fmt = u'pylot' 
    else:
      fmt = ext
    # fmt = u'pylot' if ext in [u'pylotdb'] else ext
    self.handler.saveDatabaseAs(db, fmt)

  def saveDatabase(self):
    u"""Sauvegarder la database"""
    self.handler.saveDatabase()

  def saveParameters(self):
    self.handler.saveParameters()

  def saveParametersAs(self):
    u"""  
    Save parameters (actual values only) in pylotparam format as...
    """
    if self.handler.database_file :
      default_dir = self.handler.database_file.abspath().dirname
    else :
      default_dir = Path(u'.').abspath()

    par = unicode(QFileDialog.getSaveFileName(self, u"Save parameters as...", default_dir))
    if par :
      par = Path(par)
      ext = par.ext.lower().strip('.')
      if not ext : 
          ext=u'pylotparam'
          par = par+'.'+ext
      if ext in [u'pylotparam']:
        fmt = u'pylot' 
      else:
        fmt = ext
      self.handler.saveParametersAs(par, fmt)
    else :
      return

  def setExpandState(self, expanded):
    t = QTreeWidgetItemIterator(self.tree_widget)
    while t and t.value() :
      if self.content(t.value()) in expanded :
        t.value().setExpanded(True)
      t += 1

  def expandState(self) :
    u"""
    :return: the list of actually expanded QTreeWidgetItems content, in self.tree_widget
    :rtype: list de [bloc, variable, valeur]
    """
    expanded = []
    t = QTreeWidgetItemIterator(self.tree_widget)
    while t and t.value():
      if t.value().isExpanded() :
        expanded.append(self.content(t.value()))
      t += 1
    return expanded

  def collapse(self):
    u"""Collapse all in tree_widget"""
    self.tree_widget.collapseAll()

  def expandParams(self):
    u"""Expand QTreeWidget so that parameters are visible, but values unvisible"""
    self.expand(1)

  def expandValues(self):
    u"""Expand QTreeWidget so that parameters values are visible"""
    self.expand(2)

  def expand(self, level=1):
    u"""
    - level=0 : on voit le blocs
    - level=1 : on voit les variables
    - level=2 : on voit les valeurs
    """
    self._current_expand_level = level
    self.tree_widget.collapseAll()
    t = QTreeWidgetItemIterator(self.tree_widget)
    while t and t.value():
      #print len(self.content(t.value())),...
      #...t.value().text(0), t.value().text(1), t.value().text(2)
      if len(self.content(t.value())) <= level:
        self.tree_widget.expandItem(t.value())
      t += 1
    self.adjustColumns()

  def adjustColumns(self):
    u"""Adjust column width so that all text is visible"""
    self.tree_widget.resizeColumnToContents(0)
    self.tree_widget.resizeColumnToContents(1)
    self.tree_widget.resizeColumnToContents(2)

  def _onItemActivated(self, item, column):
    u"""
    Méthode appelée lorsqu'un item du tree_widget est activé (double clic ou bien enter clavier).
      - si l'item est une valeur, appel à :meth:`setValue`
      - si l'item est un paramètre, colonne 1 : edition de la valeur
      - sinon, expand ou collapse de l'item suivant le cas
    # modif valeur courante : len(c)=2 et column = 1
    # modif nomvar : len(c)=2 et column=0
    # modif nomblock : len(c)=1
    :param item: l'item (ligne) cliqué
    :param column: la colonne cliquée
    """
    self.c = self.content(item)
    c = self.c
#        todo : il faut que c contienne les types [str, str, Datum.nature] au lieu de [str, str, str]
#        actuellement, pour un parametre de type vecteur : c vaut [u'nomblock', u'nomparam', u'1.0 2.0']
#        il faudrait [u'nomblock', u'nomparam', Vector('1.0 2.0')] ou qque chose comme ca,
    self._item_clicked = item
    #print '### GuiParameters._onItemActivated :', self.findItem(c) == item
    self._item_edited = None

    # modif valeur courante : len(c)=2 et column = 1
    # modif nomvar : len(c)=2 et column=0
    # modif nomblock : len(c)=1

    if len(c) == 3:
      self.setValue(c)
    elif len(c) == 2:
      if column == 0:
        item.setFlags(item.flags() | Qt.ItemIsEditable)
        self._item_edited = item
        #item.setExpanded (not item.isExpanded())
      elif column == 1:
        item.setFlags(item.flags() | Qt.ItemIsEditable)
        self._item_edited = item
    elif len(c) in [1]:
      item.setFlags(item.flags() | Qt.ItemIsEditable)
      self._item_edited = item
      #item.setExpanded (not item.isExpanded())


  def _onCurrentItemChanged(self, current, previous):
    u"""
    Current item in treewidget has changed.
    If previous item is parameter, column 1 contains actual value of this parameter,
    or modified (edited) value. This method allows validate this new edited value of parameter.
    :param current: current _item_clicked
    :param previous: previous item.
    :type current: QTreeWidgetItem
    :type previous: QTreeWidgetItem
    """
    if not self._item_edited : return
    self.setValue(self.content(self._item_edited))
    self._item_edited = None
    u"""self.c est le contenu sous forme [bloc,param, value] de la cellule 
    dans laquelle on arrive *avant édition* """
    self.c = self.content(current)
    #on rend previous==_item_edited non editable
    previous.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable |
                      Qt.ItemIsEnabled | Qt.ItemIsDragEnabled |
                      Qt.ItemIsDropEnabled)
    return

  def _onItemContentChanged(self, current, col):
    u"""
    Content item in treewidget has changed.
    Validate new value, updates self.handler and self.tree_widget
    :param current: current _item_clicked
    :param col: column index in current item.
    :type current: QTreeWidgetItem
    :type col: int
    """
    if not self._item_edited : return
    self.setValue(self.content(self._item_edited))
    self._item_edited = None

  def _setHandler(self, handler):
    u"""
    Affectation d'un handler.
    
    :param handler: un handler est un 'controleur-modèle' (dans la terminologie View-Model-Controller).
    
    Il contient les données 'utiles' et traite de toutes les opérations lourdes indépendantes de l'interface graphique.
      - lecture fichiers a différents formats.
      - sauvegarde, mise à jour fichiers.
      - ajout valeurs
      - etc...
      
    :type handler: :class:`~pylot.tools.Parameters`
    """
#    try :
#      path = Path(self.handler.parameters_file)
#    except ValueError : pass
#    else :
#      if path :
#        self.parameters_file = path
#    self.parameters_format = self.handler.parameters_format
#
##    try :
#    path = Path(self.handler.database_file)
##    except ValueError : pass
##    else :
##      if path :
#    self.database_file = path
#    self.database_format = self.handler.database_format

    self._toTreeView()
    self.collapse()
    self.setFocusPolicy(Qt.StrongFocus)
    self.setFocus()

  def adaptParam(self, c) :
    u"""
    Un appel à adaptValue(['block', 'var', 'val']) met la valeur de ['block']['var'] à 'val'.
    Lors de la modification d'un parametre, d'autres paramètres doivent être adapter
    :param c: list de  3 str de la forme [Block, Variable, Value] contenant la valeur modifiée.
    :type c: liste de unicode
    """
    if len(c) !=3 : return
    else :
      item = self.findItem(c[:2])
      #print self._itemToDatum(item)
      if item :
          self.addValue(item, c[2])
          item.setText(1, c[2])
          self.handler.setValue(c)
          self.emit(SIGNAL("paramValueChanged"), c)
      else: # Le paramètre n'existe pas
         item = self.findItem(c[:1]) # on cherche si le block existe
         if item :
            dat = self.handler.newParameter(c[0],c[1],unicode,c[2])
            if dat :
               self.addParameter(item, dat, new=True)
            
           

  def removeParam(self, c):
    u"""
    Suppression d'un paramètre
    :param c: parametre à supprimer
    """
    if len(c) !=2 : return
    item = self.findItem(c)
    c = self.content(item)
    self.handler.remove(c)
    try:#je sais pas faire autrement que remonter au parent et effacer l'enfant !!
      item.parent().removeChild(item)
    except AttributeError:#si ya pas de parent,
      i = self.tree_widget.indexOfTopLevelItem (item)
      self.tree_widget.takeTopLevelItem(i)

  def setValue(self, c=None) :
    u"""
    Un appel à setValue(['block', 'var', 'val']) met la valeur de ['block']['var'] à 'val'.

    Cette méthode est appellée lorsqu'un item de la base de donnees (tree_widget) a reçu un clic-droit pour ajout aux paramètres.
    La valeur est une *nouvelle* valeur d'un parametre *existant* ssi : c est de la forme [bloc, param] *et* le sender est un QTreeWidget
    L'instruction est répercutée dans l'editeur parameters et dans le navigateur database

    :param c: list de 0, 1, 2 ou 3 str de la forme [Block[, Variable[, Value]]] contenant la valeur modifiée.

      - Si c==None, on prend l'item courant, (en principe une valeur de paramètre dans un bloc) pour affecter cette valeur dans les parametres (:attr:`handler`) et dans le :attr:`text_edit`
      - si c==[bloc, var] on rajoute la valeur de la cellule cliquée

    :type c: liste de unicode
    """
    if not c : c = self._item_clickedcontent#c=[Block,Variable,Value]
    if len(c) == 0 : return
    if len(c) == 1:#Change le nom du block
      #mise a jour de la vue : c'est fait par l'utilisateur
      #mise a jour des parametres
      if type (self.sender()) == QTreeWidget :
        item = self._item_edited
        old_name = unicode(self.c[0])
        new_name = unicode(item.text(0))
        book = self.handler.pop(old_name)
        book.title = new_name
        self.handler.addBook(book)
    if len(c) == 2 :#change le nom d'un parametre ou sa valeur
      if c == self.c :#changement de valeur : on recupère la nouvelle valeur puis on rappelle setValue
        if type (self.sender()) == QTreeWidget :#c'est _onCurrentItemChanged qui appelle
          item = self._item_edited
          value = item.text(1)
          self.addValue(item, value)
          c.append(unicode(item.text(1)))
        else :
          #self.sender() est une QAction ou autre chose qui contient la nouvelle valeur,
          #et non pas un QTreeWidgetItem. On ajoute la nouvelle valeur a la suite de c
          #et on rapelle setValue avec len(c)=3.
          txt = unicode(self.sender().text()).split('=')[-1].strip()
          c.append(txt)
        self.setValue(c)#changement de la valeur du parametre
      else : #c!=self.c : on renomme une variable
        #mise a jour de la vue : c'est fait par l'utilisateur
        #mise a jour des parametres
        item = self._item_edited
        old_name = self.c[1]
        new_name = c[1]
        book_name = c[0]
        datum = self.handler.data[book_name].pop(old_name)
        datum.name = new_name
        self.handler.data[book_name][new_name] = datum

    elif len(c) == 3 :#Changement de la valeur d'un parametre
      #1- mise a jour de la vue
      #print self._item_clicked == self.findItem(c[:-1]) #quand on ne vient pas d'un clic mais d'une mise a jour par programme

      if self._item_clicked is None : #on ne vient pas d'un click mais d'une action ou une autre fonction
        item = self.findItem(c[:-1])
        if item :
          self.addValue(item, c[2])
          item.setText(1, c[2])
      else : #on vient d'un click
        if self._item_clicked.text(0) :#ya qque chose colonne 0, l'item est une variable
          self._item_clicked.setText(1, c[2])
        else : #il faut mettre à jour la ligne de la variable (le parent de _item_clicked, donc)
          self._item_clicked.parent().setText(1, c[2])

      #2- mise a jour des parameters
      self.handler.setValue(c)
    self._item_clicked = None
    #self.item_double_clicked = None
    self.emit(SIGNAL("paramValueChanged"), c)

  def _toTreeView(self):
    u"""
    Construction complète du tree_widget
    à partir du contenu de self.handler (database et parametres).
    :return: le tree_widget
    :rtype: QTreeWidget
    :todo:
      - appeler cette méthode avec parcimonie
      car elle modifie la présentation actuelle (collapse ou non).
      - A remplacer quand c'est possible par des appels à :meth:`setValue`
      - Actuellement utilisée dans :meth:`_valueToTreeView`, :meth:`saveParameters`,
      :meth:`reloadDatabase`, :meth:`suppr`.
    """
    level = self._current_expand_level
    self.tree_widget.clear()
    keys=self.handler._parameters.keys()
    keys.sort()
    for k in keys :
      block= self.handler._parameters[k]
      self.addBlock(block, new=True)
    self.expand(level)
    self.tree_widget.setSortingEnabled(1)
    return self.tree_widget

  def _itemToDatum(self, item):
    u"""
    :param item: un QTreeWidgetItem
    :return datum:un Datum correspondant à l'item ou bien None
    :type datum:Datum
    """
    content = self.content(item)
    if len(content) == 2 :
      return Datum(name=content[1], value=unicode(item.text(1)))
    else : return None


  def _itemToBook(self, item):
    u"""
    :param item: un QTreeWidgetItem
    :return book:un Book correspondant à l'item ou bien None
    :type book:Book
    """
    content = self.content(item)
    if len(content) == 1 :#c'est bien un Book
      book = Book(title=content[0])
      for index in range(item.childCount()) :
        book.add(self._itemToDatum(item.child(index)))
      return book
    else : return None

  def _treeToParameters(self):
    u"""
    :return handler: un Parameters correspondant à la vue 
    """
    raise NotImplementedError

  def duplicateBlock(self):
    u"""
    Duplicate a block clicked.
    :return: None
    """
    #Modif Gui
    clone = self._item_clicked.clone()
    content = self.content(clone)#len(content)= = 1
    name = content[0] + u'_copy'
    while self.tree_widget.findItems(name,Qt.MatchExactly)  : 
        name +=u"_copy"
    clone.setText(0, name)
    self.tree_widget.addTopLevelItem(clone)
    #Modif handler
    book = self._itemToBook(clone)
    self.handler.addBook(book)
    self.emit(SIGNAL("blockDuplicated()"))

  def duplicateParameter(self):
    u"""
    Duplicate a parameter clicked
    """
    #Modif Gui
    content = self._item_clickedcontent
    clone = self._item_clicked.clone()
    param_name = content[1]
    while self.findItem([content[0], param_name]) is not None :
      param_name += u'_copy'

    clone.setText(0, param_name)

    self._item_clicked.parent().addChild(clone)
    #Modif handler_data
    datum = self._itemToDatum(clone)
    self.handler._addParameter(content[0],datum)
    self.emit(SIGNAL("paramDuplicated()"))

  def newBlock(self):
    u"""
    Un dialogue pour définir un nouveau block.
    
    Construction du :class:`Book` correspondant, et 
    ajout du nouveau block à :attr:`self.tree_widget` et à :attr:`self.handler`
    """
    UiNewBlock, Widget = load_designer_file(u'dialognewblock.ui', [PLUGIN_PATH])
    new_block = UiNewBlock()
    dial = QDialog(self)
    new_block.setupUi(dial)
    res = dial.exec_()
    if res :
      bloc = unicode(new_block.name_lineEdit.text())
      if not bloc : return
      tooltip = unicode(new_block.tooltip_lineEdit.text())
      book = Book(title=bloc, tooltip=tooltip)
      #      book.tooltip = tooltip
      self.addBlock(book, True)

  def addBlock(self, book, new=False):
    u"""
    Ajout d'un bloc à :attr:`self.tree_widget` et/ou à :attr:`self.handler`
    
    :param book: le bloc à ajouter sous forme Book 
    :type book: Book
    :param new: = True si le bloc doit etre aussi ajouté à :attr:`self.handler`
    :type new: boolean
    """
    block = QTreeWidgetItem(self.tree_widget)
    block.setBackground(0, QBrush(self.block_color))
    block.setText(0, book.name)
    self.setToolTip(0, book.identity, block)
    values=book.keys()
    values.sort()
    for value in values:
        self.addParameter(block, book[value],new)

    self.emit(SIGNAL("blockAdded"), block)
    if new :
      self.handler._addBlock(book.title, book)
    return block

  def newParameter(self):
    u"""
    Création d'un paramètre avec dialogue de saisie.
    
    Si la boite de dialogue est validée, appel à :meth:`addParameter`
    
    :return: None
    
    
    """
    UiNewParameter, Dialog = load_designer_file(u'dialognewparameter.ui',
                                                [PLUGIN_PATH])
    new_parameter = UiNewParameter()
    dial = Dialog(self)
    new_parameter.setupUi(dial)
    res = dial.exec_()
    if not res : return
    name = unicode(new_parameter.name_lineEdit.text())
    tooltip = unicode(new_parameter.tooltip_lineEdit.text())
    nature = unicode(new_parameter.cb_value_type.currentText())
    value = unicode(new_parameter.values_lineEdit.text())
    bloc = self.content(self._item_clicked)[0]
    if nature == u'list' :
      nature = StrList
    elif nature in [u'unicode', u'string'] :
      nature = unicode
    dat = self.handler.newParameter(bloc, name, nature, value, tooltip)
    if dat :
      self.addParameter(self._item_clicked, dat, new=True)
    else :
      QMessageBox.warning(self, u"Error",
                          u"Parameter %s yet exists in block %s, cannot create it"\
                          % (name, bloc))

  def addParameter(self, block_item, parameter_datum, new=False):
    u"""
    Ajout du paramètre dans 'block_item' dans :attr:`self.tree_widget` et/ou dans :attr:`self.handler`.
    
    :param block_item: le bloc
    :type block_item: QTreeWidgetItem   
    :param parameter_datum: le parametre à ajouter, complet, sous forme de Datum
    :type parameter_datum: Datum 
    :rtype: QTreeWidgetItem
    """
    parameter = QTreeWidgetItem(block_item)
    parameter.setText(1, value_to_pylot_str(parameter_datum.value))
    parameter.setText(0, parameter_datum.name)
    self.setToolTip(0, parameter_datum, parameter)
    parameter.setForeground(0, QBrush(self.var_font_color))
    parameter.setForeground(1, QBrush(self.var_font_color))
    for val in parameter_datum.value_list :
      c = self.content(parameter)
      c.append(val)
      self.addValue(parameter, val)
    self.emit(SIGNAL("paramAdded"), block_item, parameter_datum)
    if new :
      self.handler._addParameter(unicode(block_item.text(0)), parameter_datum)
    return parameter

  def addValue(self, item, value):
    u"""
    Ajout d'une valeur value, comme fils de item, dans le tree_widget.
    
    :param item: l'item parent (parameter)
    :param value: la valeur du paramètre à ajouter en colonne 1
    :type item: QTreeWidgetItem
    :type value: str ou QString
    :return: le nouvel item
    :rtype: QTreeWidgetItem
    """
    if not value :
      return
    it = QTreeWidgetItemIterator(item)
    it += 1
    while it and it.value():
      if unicode(it.value().text(1)) == unicode(value) :
        return
      it += 1
    child = QTreeWidgetItem(item)
    child.setText(1, value_to_pylot_str(value))
    self.emit(SIGNAL("valueAdded"), child)
    return child

  def suppr(self, item=None):
    u"""
    Suppression d'un item dans le treewidget et dans la database
    :type item: QTreeWidgetItem
    :param item: l'item à supprimer
    """
    #Si l'on vient d'un menu contextuel, l'item est dans self._item_clicked
    if not item :
      item = self._item_clicked
    c = self.content(item)
    self.handler.remove(c)
    try:#je sais pas faire autrement que remonter au parent et effacer l'enfant !!
      item.parent().removeChild(item)
    except AttributeError:#si ya pas de parent,
      i = self.tree_widget.indexOfTopLevelItem (item)
      self.tree_widget.takeTopLevelItem(i)

  def _tree_widgetContextMenu(self, p) :
    u"""
    Méthode appelée lors d'un click droit sur une entree du :attr:`tree_widget`,
    affiche un menu contextuel
    :param p: emplacement du clic
    :type p: QPoint
    """
    self._item_clicked_index = self.tree_widget.indexAt(p)
    self._item_clicked = self.tree_widget.itemAt(p)
    self.c = self.content(self._item_clicked)

    menu = QMenu (self.tree_widget)
    icc = self._item_clickedcontent = self.content(self._item_clicked)
    #print '### GuiParameters._tree_widgetContextMenu :', self.findItem(icc) == self._item_clicked
#    print "GuiParameters._tree_widgetContextMenu. Len(icc)=%s" % len(icc)
    if len(icc) == 0:
      menu.addAction(self.action_new_block)
    elif len(icc) == 1 :
      self.action_duplicate_block.setText(self.tr_(u'Duplicate "%s" block') % icc[0])
      menu.addAction(self.action_duplicate_block)
      self.action_new_param.setText(self.tr_(u'New parameter in "%s" block') % icc[0])
      menu.addAction(self.action_new_param)
    if len(icc) == 2 :
      self.action_duplicate_param.setText(self.tr_(u'Duplicate "%s" parameter') % icc[1])
      menu.addAction(self.action_duplicate_param)
      value_list = self.handler.getValue(icc[0], icc[1], Datum()).value_list
      for value in value_list :
        a = QAction(self._icon(u"paramSet.png"), value_to_pylot_str(value), self)
        a.setText(self.tr_(u'Set "%s"  =  %s') % (icc[1], value))
        self.connect(a, SIGNAL('triggered()'), self.setValue)
        menu.addAction(a)
      menu.addSeparator()

    if len(icc) == 3 :
      self.action_set_value.setText(self.tr_(u'Set "') + icc[1] + u' = ' + icc[2] + u'"')
      menu.addAction(self.action_set_value)
    menu.addSeparator()
    menu.addAction(self.action_expand_params)
    menu.addSeparator()
    menu.addAction(self.action_collapse)
    menu.addAction(self.action_expand_values)
    menu.addAction(self.action_adjust_columns)
    menu.addSeparator()
    try:
      self.action_suppr.setText(self.tr_(u'Suppress "') + icc[-1] + u'"')
      menu.addAction(self.action_suppr)
    except IndexError:
      pass
    menu.exec_(self.tree_widget.mapToGlobal(p))

  def loadDatabase(self):
    if self.handler.database_file :
      default_dir = self.handler.database_file.abspath().dirname
    else :
      default_dir = Path(u'.').abspath()
    dbfile = QFileDialog.getOpenFileName(self,
      self.tr_(u'Choose pyLot database'), default_dir,
      self.tr_(u'Text files (*.pylotdb *.csv)'))

    if not dbfile :
      return

    dbfile = Path(unicode(dbfile))
    ext = dbfile.ext.lower().strip()
    if ext in [u'.pylotdb', u'.pylot']: fmt = u'pylot'
    elif ext in [u'.csv'] : fmt = u'csv'
    self.handler.readDatabase(dbfile, fmt)
    self._toTreeView()
    try :
      self.parentWidget().setWindowTitle(self.tr_(u"Parameters : %s") % dbfile)
      self.parentWidget().statusBar().showMessage(
        self.tr_(u'Working with %s') % dbfile)
    except AttributeError :
      pass

  def reloadDatabase(self):
    u"""
    Relecture de la database, annulation des dernières actions.
    """
    self.handler.reloadDatabase()
    self._toTreeView()

  def updateFrom(self):
    if self.handler.database_file:
      default_dir = self.handler.database_file.abspath().dirname
    else :
      default_dir = Path(u'.').abspath()
    parfile = QFileDialog.getOpenFileName(self,
      self.tr_(u'Choose parameters to load'), default_dir,
      self.tr_(u'Text files (*.pylotparam *.csv *.pylotdb)'))
    if not parfile : return
    parfile = Path(unicode(parfile))
    ext = parfile.ext.lower().strip('.')
    if ext in [u'pylotparam', u'pylot']:
      fmt = u'pylot'
    elif ext in [u'csv', u'pylotdb'] :
      fmt = u'csv'
    self.handler.readParameters(parfile, fmt)
    self._toTreeView()

  def setToolTip(self, col, datum, item) :
    u"""
    Tooltips dans le tree_widget, pour blocks, parameters ou values
    """
    if datum.tooltip:
      tt = self.tr_(datum.tooltip)
    else :
        try : 
            tt = self.tr_(u"What is that ???\nYou should, fill tooltip values in %s file") % Path(self.handler.database_file).name
        except TypeError :# self.handler.database_file est None
            tt = self.tr_(u"What is that ???\nYou should, fill tooltip values")
    item.setToolTip(col, tt)

if  __name__ == u"__main__":
  u"""
  pour demo, il faut un fichier 'parameters.pylot' et un 'database.csv'
  """
  from PyQt4.QtGui import QApplication
  from pylot.core import hardbook
  app = QApplication([])

  collec = Parameters()
  collec.upFromFile(hardbook(u'pylotsrc', u'pyLotTests/doctests/data/config.csv'))

  editor = GuiPart()
#  editor.setHandler(collec)
  editor.show()

  ret = app.exec_()
